
package com.base.commons.elasticsearch.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.PageUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.base.commons.elasticsearch.dao.EsBaseDao;
import com.base.commons.elasticsearch.toolkit.ReflectionKit;
import com.base.commons.tools.utils.*;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.base.commons.elasticsearch.service.EsCrudService;
import com.base.commons.tools.constant.Constant;
import com.base.commons.tools.excel.ExcelDynamicHeader;
import com.base.commons.tools.excel.headerparse.ExcelDynamicHeadValueParse;
import com.base.commons.tools.exception.BaseOfException;
import com.base.commons.tools.page.PageData;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.frameworkset.elasticsearch.entity.ESDatas;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * CRUD基础服务类
 *
 * @author carl
 */
@Slf4j
public abstract class EsCrudServiceImpl<M extends EsBaseDao<T>, T, D> extends EsBaseServiceImpl<M, T> implements EsCrudService<T, D> {
    /**
     * 获取当前服务名称
     */
    @Value("${spring.application.name:service}")
    public String serviceName;

    protected Class<D> currentDtoClass() {
        return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), 2);
    }

    @Override
    public PageData<D> esPage(Object dto) {
        List<T> tList;
        Map<String, Object> paramMap = ReflectionKit.getPageMapByObject(dto);
        int page = Convert.toInt(paramMap.get(Constant.PAGE));
        int limit = Convert.toInt(paramMap.get(Constant.LIMIT));
        String searchAfterId = Convert.toStr(paramMap.get(Constant.SEARCH_AFTER_ID));
        String searchBeforeId = Convert.toStr(paramMap.get(Constant.SEARCH_BEFORE_ID));
        int[] fromSize = PageUtil.transToStartEnd(page, limit);
        paramMap.put(Constant.FROM, fromSize[0]);
        Long total = selectCount(paramMap);
        if (fromSize[0] + fromSize[1] <= DEFAULT_ES_SIZE && searchAfterId == null && searchBeforeId == null) {
            //ESDatas包含当前检索的记录集合，最多1000条记录，由dsl中的size属性指定
            //返回的文档封装对象类型
            tList = searchList(paramMap).getDatas();
        } else {
            paramMap.put(Constant.FROM, 0);
            //ESDatas包含当前检索的记录集合，最多1000条记录，由dsl中的size属性指定
            tList = searchList(paramMap).getDatas();
        }
        return getPageData(tList, total, currentDtoClass());
    }

    @Override
    public <S> String[] exportCsvScroll(String csvPathId,
                                        Object dto,
                                        Long size, Long pageSize, Class<S> source) throws Exception {
        String newCsvPath = FileUtil.rootPath() + File.separator + serviceName + File.separator + "csvFile" + File.separator + DateUtils.format(new Date(), "yyyyMMdd") + File.separator + csvPathId;
        // 创建csv 对象 大于100万重新新建csv
        //scroll分页检索
        Map<String, Object> paramMap = ReflectionKit.getPageMapByObject(dto);
        Set<String> scrollIds = new TreeSet<>();
        long startTime = System.currentTimeMillis();
        //每页100条记录
        size = (size == null ? DEFAULT_ES_SIZE : size);
        paramMap.put(SIZE, size);
        ESDatas<T> response = searchListScroll(paramMap);
        List<T> oneData = response.getDatas();
        if (oneData == null) {
            throw new BaseOfException("查询数据为空,不能导出");
        }
        //实际页数
        long realTotalSize = oneData.size();
        //总页数
        long totalSize = response.getTotalSize();
        //滚动id
        String scrollId = response.getScrollId();
        if (scrollId != null) {
            scrollIds.add(scrollId);
        }
        pageSize = (pageSize == null ? DEFAULT_EXPORT_ES_SIZE : pageSize);
        long totalPage = totalSize >= 1 ? (totalSize + pageSize - 1) / pageSize : 1;
        for (int i = 1; totalPage >= i; i++) {
            try {
                //创建文件目录
                if (!Files.exists(Paths.get(newCsvPath))) {
                    Files.createDirectories(Paths.get(newCsvPath));
                }
                //每页1000条记录，迭代scrollid，遍历scroll分页结果
                if (oneData != null && oneData.size() > 0) {
                    CsvOpenUtils.exportCsvToTarget(newCsvPath, String.valueOf(i-1), oneData, source);
                }
                do {
                    response = searchScroll(SCROLL, scrollId);
                    scrollId = response.getScrollId();
                    if (scrollId != null) {
                        scrollIds.add(scrollId);
                    }
                    List<T> datas = response.getDatas();
                    if (datas != null) {
                        CsvOpenUtils.exportCsvToTarget(newCsvPath, String.valueOf(i-1), datas, source);
                    }
                    if (datas == null || datas.size() == 0) {
                        break;
                    }
                    realTotalSize = realTotalSize + datas.size();
                    if ((i) * pageSize <= realTotalSize) {
                        oneData = null;
                        break;
                    }
                } while (true);
            } catch (Exception e) {
//                e.printStackTrace();
                log.error("csv文件导出异常:{}",e);
                throw e;
            }
        }
        //处理完毕后清除scroll上下文信息
        if (scrollIds.size() > 0) {
            deleteScrolls(scrollIds);
        }
        long endTime = System.currentTimeMillis();
        log.info("耗时：{}realTotalSize：{},totalSize:{}", endTime - startTime, realTotalSize, totalSize);
        File files = new File(newCsvPath);
        String[] strings = Arrays.stream(files.listFiles()).map(file -> file.getAbsolutePath()).collect(Collectors.toList()).toArray(new String[files.list().length]);
        // 如果文件全部导出失败，清理本地文件和文件夹
//        if (Convert.toInt(totalPage) != strings.length) {
//            FileUtil.delDir(newCsvPath);
//        }
        return strings;
    }

    @Override
    public <S> String[] exportCsvScrollDynamicTitle(String csvPathId, Object dto, Long size, Long pageSize, Class<S> source,List<List<String>> dynamicHeader) throws Exception {
        String newCsvPath = FileUtil.rootPath() + File.separator + serviceName + File.separator + "csvFile" + File.separator + DateUtils.format(new Date(), "yyyyMMdd") + File.separator + csvPathId;
        // 创建csv 对象 大于100万重新新建csv
        //scroll分页检索
        Map<String, Object> paramMap = ReflectionKit.getPageMapByObject(dto);
        Set<String> scrollIds = new TreeSet<>();
        long startTime = System.currentTimeMillis();
        //每页100条记录
        size = (size == null ? DEFAULT_ES_SIZE : size);
        paramMap.put(SIZE, size);
        /**
         * 获取完整表头
         */

        ESDatas<T> response = searchListScroll(paramMap);
        List<T> oneData = response.getDatas();
        if (oneData == null) {
            throw new BaseOfException("查询数据为空,不能导出");
        }
        //实际页数
        long realTotalSize = oneData.size();
        //总页数
        long totalSize = response.getTotalSize();
        //滚动id
        String scrollId = response.getScrollId();
        if (scrollId != null) {
            scrollIds.add(scrollId);
        }
        pageSize = (pageSize == null ? DEFAULT_EXPORT_ES_SIZE : pageSize);
        long totalPage = totalSize >= 1 ? (totalSize + pageSize - 1) / pageSize : 1;
        //根据bean获取Excel头
        List<List<String>> excelHeads = EasyExcelUtils.getExcelHeadByClass(source);
        excelHeads.addAll(dynamicHeader);
        for (int i = 1; totalPage >= i; i++) {
            ExcelWriter excelWriter = null;
            try {
                Path myPath = Paths.get(newCsvPath + File.separator + (i - 1) + ".xlsx");
                //创建文件目录
                if (!Files.exists(Paths.get(newCsvPath))) {
                    Files.createDirectories(Paths.get(newCsvPath));
                }
                excelWriter = EasyExcel.write(myPath.toFile()).build();
                WriteSheet sheet = EasyExcel.writerSheet("sheet")
                        //自动列宽
                        .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                        .head(excelHeads)
                        .build();
                //每页1000条记录，迭代scrollid，遍历scroll分页结果
                if (oneData != null && oneData.size() > 0) {
                    List<Object> objects = generateDynamicVariable(oneData, dynamicHeader,source);
                    excelWriter.write(objects, sheet);
                }
                do {
                    response = searchScroll(SCROLL, scrollId);
                    scrollId = response.getScrollId();
                    if (scrollId != null) {
                        scrollIds.add(scrollId);
                    }
                    List<T> datas = response.getDatas();
                    if (CollectionUtils.isEmpty(datas)) {
                        break;
                    }
                    List<Object> objects = generateDynamicVariable(datas, dynamicHeader,source);
                    excelWriter.write(objects, sheet);
                    realTotalSize = realTotalSize + datas.size();
                    if ((i) * pageSize <= realTotalSize) {
                        oneData = null;
                        break;
                    }
                } while (true);
            } catch (Exception e) {
                log.error("csv文件导出异常:{}",e);
                throw e;
            } finally {
                if (excelWriter != null) {
                    excelWriter.finish();
                }
            }
        }
        //处理完毕后清除scroll上下文信息
        if (scrollIds.size() > 0) {
            deleteScrolls(scrollIds);
        }
        long endTime = System.currentTimeMillis();
        log.info("耗时：{}realTotalSize：{},totalSize:{}", endTime - startTime, realTotalSize, totalSize);
        File files = new File(newCsvPath);
        String[] strings = Arrays.stream(files.listFiles()).map(file -> file.getAbsolutePath()).collect(Collectors.toList()).toArray(new String[files.list().length]);
        // 如果文件全部导出失败，清理本地文件和文件夹
//        if (Convert.toInt(totalPage) != strings.length) {
//            FileUtil.delDir(newCsvPath);
//        }
        return strings;
    }


   /* private List<List<String>> queryTableHeader(Map<String, Object> paramMap, Class target) {
        List<List<String>> dynamicHeader = Lists.newArrayList();
        paramMap.put("queryTitle", "cost_fee_detail_count");
        paramMap.put("from", 0);
        ESDatas<T> cost = searchList(paramMap);
        paramMap.put("queryTitle", "income_fee_detail_count");
        ESDatas<T> income = searchList(paramMap);
        paramMap.remove("queryTitle");
        paramMap.remove("from");
        try {
            if (CollectionUtils.isNotEmpty(cost.getDatas())) {
                T t = cost.getDatas().get(0);
                List<List<String>> lists = initTableHeader(t, "costFeeDetail", "(成本)");
                dynamicHeader.addAll(lists);
            }
            if (CollectionUtils.isNotEmpty(income.getDatas())) {
                T t = income.getDatas().get(0);
                List<List<String>> lists = initTableHeader(t, "incomeFeeDetail", "(收入)");
                dynamicHeader.addAll(lists);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dynamicHeader;
    }*/

    public static List<List<String>> initTableHeader(Object obj, String fieldName, String alias) {
        List<List<String>> dynamicHeader = Lists.newArrayList();
        String costFeeDetail;
        try {
            costFeeDetail = org.apache.commons.beanutils.BeanUtils.getProperty(obj, fieldName);
            String[] costFee = costFeeDetail.split(",");
            List<String> title;
            for (String fee : costFee) {
                String[] feeDetail = fee.split(":");
                title = Lists.newArrayList();
                title.add(feeDetail[1] + alias);
                dynamicHeader.add(title);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dynamicHeader;
    }

    private static List<ExcelDynamicHeadValueParse> excelDynamicHeadParse = new ArrayList<>();

    /**
     * @param objectList
     * @param excelHead 动态部分表头
     * @param targetClazz
     * @param <T>
     * @return
     */
    public static Object o = new Object();
    public static <T> List<Object> generateDynamicVariable(List<T> objectList, List<List<String>> excelHead,Class targetClazz) {
        if (CollectionUtils.isEmpty(objectList)) {
            return null;
        }
        List<Field> dynamicField = Arrays.stream(targetClazz.getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(ExcelDynamicHeader.class))
                .peek(field -> field.setAccessible(true))
                .collect(Collectors.toList());
        List<Object> excelDataList = ConvertUtils.convert(objectList, targetClazz);
        List<Object> targetObjectList = excelDataList.stream().map(obj -> {
            Map<String, Object> header = new HashMap<>();
            for (Field field : dynamicField) {
                try {
                    Object data = field.get(obj);
                    if (StringUtils.isEmpty(data)) {
                        continue;
                    }
                    ExcelDynamicHeader excelDynamicHeader = field.getAnnotation(ExcelDynamicHeader.class);
                    Class<? extends ExcelDynamicHeadValueParse> parseValueHeader = excelDynamicHeader.parseValueHeader();
                    ExcelDynamicHeadValueParse valueParse = excelDynamicHeadParse.stream()
                            .filter(parse -> parse.getClass().equals(parseValueHeader))
                            .findAny()
                            .orElseGet(() -> {
                                ExcelDynamicHeadValueParse obj1 = null;
                                try {
                                    obj1 = parseValueHeader.newInstance();
                                    excelDynamicHeadParse.add(obj1);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                                return obj1;
                            });
                    header.putAll(valueParse.parseValue(data, excelDynamicHeader.alias()));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Map<String, Object> addProperties = Maps.newLinkedHashMap();
            Field[] declaredFields = obj.getClass().getDeclaredFields();
            Arrays.stream(declaredFields).filter(field -> !field.isAnnotationPresent(ExcelIgnore.class)).forEach(new Consumer<Field>() {
                @Override
                public void accept(Field field) {
                    field.setAccessible(true);
                    try {
                        addProperties.put(field.getName(), field.get(obj));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            });
            AtomicInteger index = new AtomicInteger(1);
            excelHead.stream()
                    .flatMap((Function<List<String>, Stream<String>>) strings -> strings.stream())
                    .forEach(s -> addProperties.put("field"+(index.getAndIncrement()), header.get(s)==null? "":header.get(s)));
            addProperties.remove("serialVersionUID");
            Object target = ReflectUtil.getTarget(o, addProperties);
            return target;
        }).collect(Collectors.toList());
        return targetObjectList;
    }
}